/****************************************************************************************
**  File: Mesh.hpp
**  Author: Asteroth
**  Date: 11-mag-2009
****************************************************************************************/

#ifndef __MESH_HPP__
#define __MESH_HPP__

enum MeshType
{
	Static = 0,
	Skin = 1
};

class Mesh {
private:
	estd::String		name;
	MeshType			type;
	core::Matrix4x4		modelView;
	int					bone;
	Texture				texture;
	Material			material;
	bool				loaded;

	estd::Pointer<unsigned int>		indexData;
	estd::Pointer<float> 			vertexData;
	estd::Pointer<float> 			texCoordData;
	estd::Pointer<float> 			normalData;
	estd::Pointer<unsigned int>		boneIndexData;
	estd::Pointer<float>			boneWeightData;

	estd::Pointer<float>			transformedVertexData;
	estd::Pointer<float>			transformedNormalData;

public:
	Mesh() {
		type = Static;
		transformedVertexData = vertexData;
		transformedNormalData = normalData;
		bone = -1;
		loaded = false;
	}

	inline uint getIndexCount() const {return indexData.size() / 3;}
	inline uint getVertexCount() const {return vertexData.size() / 3;}

	inline MeshType getType() {return type;}
	inline void setType(MeshType type) {this->type = type;}

	estd::Pointer<unsigned int>		getIndexData() const			{return indexData;}
	estd::Pointer<float>			getVertexData() const			{return vertexData;}
	estd::Pointer<float>			getTexCoordData() const 		{return texCoordData;}
	estd::Pointer<float>			getNormalData() const			{return normalData;}
	estd::Pointer<float>			getBoneWeightData() const		{return boneWeightData;}
	estd::Pointer<unsigned int>		getBoneIndexData() const		{return boneIndexData;}

	void setIndexData(const estd::Pointer<unsigned int> &data)		{indexData = data;}
	void setVertexData(const estd::Pointer<float> &data)			{vertexData = data;}
	void setTexCoordData(const estd::Pointer<float> &data)			{texCoordData = data;}
	void setNormalData(const estd::Pointer<float> &data)			{normalData = data;}
	void setBoneWeightData(const estd::Pointer<float> &data)		{boneWeightData = data;}
	void setBoneIndexData(const estd::Pointer<unsigned int> &data)	{boneIndexData = data;}

	bool update(estd::Pointer<Bone*> &bones) {
		if (!loaded)
			return false;

		if (type == Static) {
			transformedVertexData = vertexData;
			transformedNormalData = normalData;

			Bone* bn = *bones[bone];
			if (bn==NULL)
				bn = (Bone*)&IDENTITY_BONE;

			modelView = bn->finalTransformation;

			return true;
		} else if (type == Skin) {
			if (vertexData.size() / 3 != boneIndexData.size() / 4 || vertexData.size() / 3 != boneWeightData.size() / 4)
				return false;

			int vSize = vertexData.size();
			int nSize = normalData.size();

			if (transformedVertexData==vertexData)
				transformedVertexData.realloc(vSize);
			if (transformedNormalData==normalData)
				transformedNormalData.realloc(nSize);

			core::Vector3 in, out;
			Bone *bn;

			// Transforming vertices
			float* vd = vertexData[0];
			float* td = transformedVertexData[0];

			for (int v = 0, b = 0; v < vSize; v += 3, b += 4) {
				in.X = vd[v];
				in.Y = vd[v+1];
				in.Z = vd[v+2];

				bn = *bones[*boneIndexData[b]];
				out = in.transformed(bn->finalTransformation) * (*boneWeightData[b]);
				bn = *bones[*boneIndexData[b+1]];
				if (bn!=NULL)
				{
					out = in.transformed(bn->finalTransformation) * (*boneWeightData[b+1]);
					bn = *bones[*boneIndexData[b+2]];
					if (bn!=NULL)
					{
						out = in.transformed(bn->finalTransformation) * (*boneWeightData[b+2]);
						bn = *bones[*boneIndexData[b+3]];
						if (bn!=NULL)
							out = in.transformed(bn->finalTransformation) * (*boneWeightData[b+3]);
					}
				}

				td[v] = out.X;
				td[v+1] = out.Y;
				td[v+2] = out.Z;
			}

			// Transforming normals
			vd = normalData[0];
			td = transformedNormalData[0];

			for (int v = 0, b = 0; v < vSize; v += 3, b += 4) {
				in.X = vd[v];
				in.Y = vd[v+1];
				in.Z = vd[v+2];

				bn = *bones[*boneIndexData[b]];
				out = in.rotated(bn->finalTransformation) * (*boneWeightData[b]);
				bn = *bones[*boneIndexData[b+1]];
				if (bn!=NULL)
				{
					out = in.rotated(bn->finalTransformation) * (*boneWeightData[b+1]);
					bn = *bones[*boneIndexData[b+2]];
					if (bn!=NULL)
					{
						out = in.rotated(bn->finalTransformation) * (*boneWeightData[b+2]);
						bn = *bones[*boneIndexData[b+3]];
						if (bn!=NULL)
							out = in.rotated(bn->finalTransformation) * (*boneWeightData[b+3]);
					}
				}

				td[v] = out.X;
				td[v+1] = out.Y;
				td[v+2] = out.Z;
			}

			modelView.makeIdentity();

			return true;
		}
	}

	inline bool draw() {
		if (!loaded)
			return false;

		glLoadMatrixf(modelView.M);

		texture.activate();
		material.activate();

		glEnableClientState(GL_VERTEX_ARRAY);
		glVertexPointer(3, GL_FLOAT, 0, transformedVertexData[0]);

		if (texCoordData.size() > 0) {
			glEnableClientState(GL_TEXTURE_COORD_ARRAY);
			glTexCoordPointer(2, GL_FLOAT, 0, texCoordData[0]);
		} else
			glDisableClientState(GL_TEXTURE_COORD_ARRAY);

		if (transformedNormalData.size() > 0) {
			glEnableClientState(GL_NORMAL_ARRAY);
			glNormalPointer(GL_FLOAT, 0, transformedNormalData[0]);
		} else
			glDisableClientState(GL_NORMAL_ARRAY);

		glDrawElements(GL_TRIANGLES, indexData.size(), GL_UNSIGNED_INT, indexData[0]);

		return true;
	}

};

#endif
